package com.jsy.common.util.bytecode;

import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.MethodCall;
import javassist.expr.NewExpr;

/**
 *动态修改字节码
 *Javassist 是一个开源的 Java 字节码操作库，它允许开发者在运行时对 Java 类的字节码进行动态修改和生成
 特性	Javassist	ASM
 抽象层级	源码级操作（类似字符串拼接）	字节码指令级操作
 易用性	高（适合简单场景）	低（需理解字节码结构）
 灵活性	有限（无法处理复杂指令）	高（可精细控制每一条指令）
 性能	较低（适合离线操作）	高（适合运行时动态生成）
 调试信息支持	弱（依赖编译器参数）	强（可直接操作 LineNumberTable）
 1. Javassist 是一个开源的 Java 字节码操作库，它允许开发者在运行时对 Java 类的字节码进行动态修改和生成
 2. 在不解压的情况下，更新jar包某个文件，可以直接通过jar 更新指令对某个文件修改：
 jar -uvf shop-order-0.0.1-SNAPSHOT.jar BOOT-INF/classes/application.properties
 jar -uvf kkFileView.jar BOOT-INF/classes/application.properties
 */
public class JavassistModifier {
    public static void main(String[] args) throws Exception {
        //获取默认的类池。类池的作用是存储和查找类的字节码，它会维护一个类名到 CtClass 对象的映射。
        ClassPool pool = ClassPool.getDefault();
        //添加额外的类路径
        // 目录：包含 .class 文件的根目录（如 target/classes）。
        //JAR 文件：如 my-library.jar。
        //通配符路径：如 libs/*（匹配目录下所有 JAR 文件）。
        //动态生成的类：通过字节码工具（如 Javassist）生成的类。
        // 加载目录中的 .class 文件
//        ClassPath classPath = pool.appendClassPath("F:\\jsy\\goalong\\goalong-common\\src\\main\\lib");
        // 加载目录中的所有 JAR 文件
        ClassPath classPath = pool.appendClassPath("F:\\jsy\\goalong\\goalong-common\\src\\main\\lib\\*");
        // 加载 JAR 文件
//        ClassPath classPath = pool.appendClassPath("F:\\jsy\\goalong\\goalong-common\\src\\main\\lib\\jimureport-spring-boot-starter-1.9.0.jar");
//        CtClass ctClass = example(pool);
        //这是 ClassPool 类的一个方法，用于根据类的全限定名（即包含包名的类名）查找对应的 CtClass 对象。
        //若类池里已经存在该类的 CtClass 对象，就直接返回；若不存在，类池会尝试从类路径中加载该类的字节码，并创建对应的 CtClass 对象。
        CtClass ctClass = pool.get("org.jeecg.modules.jmreport.desreport.util.h");

        // 获取目标方法
//        CtMethod method = ctClass.getDeclaredMethod("a");
        CtMethod[] methods = ctClass.getDeclaredMethods("b");
        System.out.println("methods.length = " + methods.length);
        for (CtMethod method : methods) {
            String signature = method.getSignature();
//            String longName = method.getLongName();
            System.out.println("signature: " + signature);
//            System.out.println("longName: " + longName);
            // 修改指定方法签名的方法体
            if ("(Ljavax/servlet/http/HttpServletRequest;)Z".equals(signature)){
                System.out.println("111MethodName: " + method.getLongName());
                method.setBody("{return true;}");
            }
            // 获取方法体并打印
//            method.instrument(new ExprEditor(){
//                @Override
//                public void edit(MethodCall m) {
//                    System.out.println("MethodName: " + m.getMethodName());
//                    System.out.println("MethodCall: " + m.getSignature());
//                    System.out.println("MethodLine: " + m.getLineNumber());
//                }
//            });

        }
//        printMethodBody("org.jeecg.modules.jmreport.desreport.util.h", "a");
        // 在方法开头插入代码
//        method.insertBefore("System.out.println(\"Method myMethod started\");");

        // 在第 3 行（字节码行号）后插入代码
//        method.insertAt(3, "{ System.out.println(\"插入的日志\"); }");
        // 示例：将第5行的 System.out.println("旧内容") 替换为新内容


        // 基于抽象语法树（AST）精确修改
//        replaceLine(method);
        // 保存修改后的类
        ctClass.writeFile("F:\\jsy\\goalong\\goalong-common\\target\\output");
        //修改后需调用，防止内存泄漏
        ctClass.detach();
    }

    // 示例：创建一个动态类并添加字段和方法
    private static CtClass addMethod(ClassPool pool) throws CannotCompileException {
        CtClass ctClass = pool.makeClass("DynamicExample");

        // 添加字段
        CtField field = new CtField(CtClass.intType, "count", ctClass);
        ctClass.addField(field);

        // 添加方法
        CtMethod method = CtNewMethod.make(
                "public void printCount() { System.out.println(count); }",
                ctClass
        );
        ctClass.addMethod(method);
        return ctClass;
    }

    private static void replaceBody(CtMethod method) throws CannotCompileException {
        String originalMethodBody = method.getMethodInfo().getCodeAttribute().toString();
        // 替换特定行的字符串操作
        String modifiedBody = originalMethodBody.replaceAll(
                "String str = \"old\"; // 源代码第8行",
                "String str = \"new\"; // 修改后内容"
        );
        method.setBody(modifiedBody);
    }

    public static void printMethodBody(String className, String methodName) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(className);
        CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);

        // 打印方法体（需要行号表等调试信息）
        System.out.println("方法体源码（近似）:");
        System.out.println(ctMethod.getMethodInfo().getCodeAttribute());
    }

    private static void replaceLine(CtMethod method) throws CannotCompileException {
        method.instrument(
                new ExprEditor() {
                    @Override
                    public void edit(MethodCall call) throws CannotCompileException {
                        // 定位特定方法调用
//                        if (call.getClassName().equals("java.io.PrintStream")
//                                && call.getMethodName().equals("println")
//                                && call.getLineNumber() == 5) { // 根据行号过滤
//                            call.replace("{ System.out.println(\"新内容\"); }");
//                        }
                        // 在所有方法调用前后插入计时逻辑
                        String code = String.format(
                                "{ long start = System.nanoTime(); " +
                                        "$_ = $proceed($$); " + // 执行原方法调用
                                        "System.out.println(\"方法 %s.%s 耗时: \" + (System.nanoTime() - start) + \" ns\"); }",
                                call.getClassName(), call.getMethodName()
                        );
                        call.replace(code);
                    }
                    @Override
                    public void edit(NewExpr e) throws CannotCompileException {
                        // 修改所有 new 操作（例如替换对象创建逻辑）
                        if (e.getLineNumber() == 7) { // 定位行号
                            e.replace("{ $_ = new com.example.NewObject(); }");
                        }
                    }

                    @Override
                    public void edit(FieldAccess f) throws CannotCompileException {
                        // 修改字段访问（例如拦截敏感数据访问）
                        if (f.getLineNumber() == 12) {
                            f.replace("{ System.out.println(\"字段被访问\"); $proceed($$); }");
                        }
                    }
                }
        );
    }
}